﻿/**
 * Fader
 *
 * @author		Allen Chou
 * @version		0.1.0 (last update: Jun 7 2008)
 * @link        http://cjcat.blogspot.com
 * @link		http://cjcat2266.deviantart.com
 */

package idv.cjcat.utils.fader {
	import flash.display.*;
	import flash.errors.IllegalOperationError;
	import flash.events.*;
	
	import idv.cjcat.*;
	import idv.cjcat.easing.*;
	
	public class Fader {
		
		public var property:String;
		public var restore:Boolean;
		
		private var _pointers:Array;
		
		private var _peakValue:Number;
		private var _rimValue:Number;
		
		private var _endL:Number;
		private var _endR:Number;
		
		private var _objects:Array = new Array();
		
		public function Fader(property:String = null, rimValue:Number = 0, peakValue:Number = 1, endL:Number = -100, endR:Number = 100, pointers:Array = null, restore:Boolean = true) {
			if (pointers == null) {
				this.pointers = new Array();
			} else {
				this.pointers = pointers;
			}
			
			this.rimValue = rimValue;
			this.peakValue = peakValue;
			this.endL = endL;
			this.endR = endR;
			this.property = property;
			this.restore = restore;
		}
		
		//public methods
		//------------------------------------------------------------------------------------------------
		
		public function add(obj:DisplayObject, pos:Number):DisplayObject {
			remove(obj);
			
			//[object, position, object's property]
			_objects.push([obj, pos, obj[property]]);
			_objects.sort(objComparator);
			
			return obj;
		}
		
		public function remove(obj:DisplayObject):DisplayObject {
			if (_objects.length == 0) return obj;
			for (var i:int = 0; i < _objects.length; i++) {
				if (_objects[i][0] === obj) {
					//resotres property
					if (restore) obj[property] = _objects[i][property];
					_objects.splice(i, 1);
					return obj;
				}
			}
			return obj;
		}
		
		public function removeAll():void {
			while(_objects.length > 0) {
				remove(_objects[0][0]);
			}
		}
		
		public function getPosition(obj:DisplayObject):Number {
			if (_objects.length == 0) return NaN;
			for (var i:int = 0; i < _objects.length; i++) {
				if (_objects[i][0] === obj) {
					return _objects[i][1];
				}
			}
			return NaN;
		}
		
		public function render():void {
			if (_objects.length == 0) return;
			if (_pointers.length == 0) return;
			
			for (var j:int = 0; j < _objects.length; j++) {
				_objects[j][0][property] = _rimValue;
			}
			
			update_useBand_linear(this);
		}
		
		/*
		public function render():void {
			if (_objects.length == 0) return;
			if (_pointers.length == 0) return;
			
			for (var j:int = 0; j < _objects.length; j++) {
				_objects[j][0][property] = _rimValue;
			}
			
			for (var i:int = 0; i < _pointers.length; i++) {
				_pointers[i].update(this, i);
			}
		}
		*/
		
		public function clone():Fader {
			var temp:Fader = new Fader();
			temp.property = property;
			temp.restore = restore;
			temp._pointers = _pointers;
			temp._peakValue = _peakValue;
			temp._rimValue = _rimValue;
			temp._endL = _endL;
			temp._endR = _endR;
			temp._objects = _objects;
			return temp;
		}
		
		//end of public methods
		//------------------------------------------------------------------------------------------------
		
		
		//internal methods
		//------------------------------------------------------------------------------------------------
		
		internal static function update_useBand_linear(fader:Fader):void {
			var divisor:Number = fader._pointers.length;
			var interval:Number = fader._peakValue - fader._rimValue;
			
			var i:int;
			for (var index:int = 0; index < fader._pointers.length; index++) {
					
				if (fader._pointers[index].circular) {
					
					var endInterval:Number = fader._endR - fader._endL;
					
					var clone:FaderPointer = fader._pointers[index].clone();
					
					var mod:Number = (clone._position - fader._endL) % endInterval;
					if (mod < 0) mod += endInterval;
					if (clone._position < fader._endL || clone._position > fader._endR) clone._position = fader._endL + mod;
					
					var clone2:FaderPointer = clone.clone();
					if ((clone._position - fader._endL) > endInterval / 2) {
						clone2._position = clone._position - endInterval;
					} else {
						clone2._position = clone._position + endInterval;
					}
					
					//trace(clone.position, clone2.position, mod);
					
					for (i = 0; i < fader._objects.length; i++) {
						//clone
						if (fader._objects[i][1] <= clone._position) {
							d = clone._position - fader._objects[i][1];
							if (d <= clone._bandwidthL) {
								fader._objects[i][0][fader.property] += (clone._easingL(d, fader._peakValue, -interval, clone._bandwidthL) - fader._rimValue) / divisor;
							}
						} else {
							d = fader._objects[i][1] - clone._position;
							if (d <= clone._bandwidthR) {
								fader._objects[i][0][fader.property] += (clone._easingR(d, fader._peakValue, -interval, fader._pointers[index]._bandwidthR) - fader._rimValue) / divisor;
							}
						}
						
						//clone2
						if (fader._objects[i][1] <= clone2._position) {
							d = clone2._position - fader._objects[i][1];
							if (d <= clone2._bandwidthL) {
								fader._objects[i][0][fader.property] += (clone2._easingL(d, fader._peakValue, -interval, clone2._bandwidthL) - fader._rimValue) / divisor;
							}
						} else {
							d = fader._objects[i][1] - clone2._position;
							if (d <= clone2._bandwidthR) {
								fader._objects[i][0][fader.property] += (clone2._easingR(d, fader._peakValue, -interval, fader._pointers[index]._bandwidthR) - fader._rimValue) / divisor;
							}
						}
					}
					
					
				} else {
					
					
					for (i = 0; i < fader._objects.length; i++) {
						if (fader._objects[i][1] <= fader._pointers[index]._position) {
							d = fader._pointers[index]._position - fader._objects[i][1];
							if (d > fader._pointers[index]._bandwidthL) {
								continue;
							}
							fader._objects[i][0][fader.property] += (fader._pointers[index]._easingL(d, fader._peakValue, -interval, fader._pointers[index]._bandwidthL) - fader._rimValue) / divisor;
						} else {
							d = fader._objects[i][1] - fader._pointers[index]._position;
							if (d > fader._pointers[index]._bandwidthR) {
								continue;
							}
							fader._objects[i][0][fader.property] += (fader._pointers[index]._easingR(d, fader._peakValue, -interval, fader._pointers[index]._bandwidthR) - fader._rimValue) / divisor;
						}
					}
					
				}
			}
		}
		
		/*
		internal static function update_useBand_linear(fader:Fader, index:int):void {
			var divisor:Number = fader._pointers.length;
			
			var interval:Number = fader._peakValue - fader._rimValue;
			for (var i:int = 0; i < fader._objects.length; i++) {
				if (fader._objects[i][1] <= fader._pointers[index]._position) {
					d = fader._pointers[index]._position - fader._objects[i][1];
					if (d > fader._pointers[index]._bandwidthL) {
						continue;
					}
					fader._objects[i][0][fader.property] += (fader._pointers[index]._easingL(d, fader._peakValue, -interval, fader._pointers[index]._bandwidthL) - fader._rimValue) / divisor;
				} else {
					d = fader._objects[i][1] - fader._pointers[index]._position;
					if (d > fader._pointers[index]._bandwidthR) {
						continue;
					}
					fader._objects[i][0][fader.property] += (fader._pointers[index]._easingL(d, fader._peakValue, -interval, fader._pointers[index]._bandwidthR) - fader._rimValue) / divisor;
				}
			}
		}
		*/
		
		/*
		internal static function update_default_linear(fader:Fader, index:int):void {
			var divisor:Number = fader._pointers.length;
			
			var temp:Number;
			
			var lowerIndex:int = 0;
			var found:Boolean = false;
			for (var i:int = 0; i < fader._objects.length; i++) {
				if (fader._objects[i][1] > fader._pointers[index]._position) {
					if (!found) {
						lowerIndex = i - 1;
						found = true;
					}
				}
			}
			if (lowerIndex == -1) {
				fader._objects[0][0][fader.property] += (fader._peakValue - fader._rimValue) / divisor;
				return;
			} else if (!found) {
				fader._objects[fader._objects.length - 1][0][fader.property] += (fader._peakValue - fader._rimValue) / divisor;
				return;
			}
			
			temp = CJMath.interpolate(fader._objects[lowerIndex][1], fader._rimValue,
									  fader._objects[lowerIndex + 1][1], fader._peakValue, fader._pointers[index]._position)
			
			fader._objects[lowerIndex][0][fader.property] += (fader._peakValue - temp) / divisor;
			fader._objects[lowerIndex + 1][0][fader.property] += (-fader._rimValue + temp) / divisor;
		}
		*/
		
		//end of internal methods
		//------------------------------------------------------------------------------------------------
		
		
		//internal functions
		//------------------------------------------------------------------------------------------------
		
		private static function objComparator(a:Array, b:Array):Number {
			if (a[1] < b[1]) return -1;
			return 1;
		}
		
		//end of internal functions
		//------------------------------------------------------------------------------------------------
		
		
		//accessors
		//------------------------------------------------------------------------------------------------
		
		public function get rimValue():Number {
			return _rimValue;
		}
		
		public function set rimValue(value:Number):void {
			_rimValue = CJMath.clamp(value, -Number.MAX_VALUE, _peakValue);
		}
		
		public function get peakValue():Number {
			return _peakValue;
		}
		
		public function set peakValue(value:Number):void {
			_peakValue = CJMath.clamp(value, _rimValue, Number.MAX_VALUE);
		}
		
		public function get endL():Number {
			return _endL;
		}
		
		public function set endL(value:Number):void {
			_endL = CJMath.clamp(value, -Number.MAX_VALUE, _endR);
			for (var i:int = 0; i < _pointers.length; i++) {
				if (_pointers[i].bandwidth > _endR - _endL) throw new IllegalOperationError("the FaderPointers's bandwidth exceeds the interval between the Fader's endL and endR.");
			}
		}
		
		public function get endR():Number {
			return _endR;
		}
		
		public function set endR(value:Number):void {
			_endR = CJMath.clamp(value, _endL, Number.MAX_VALUE);
			for (var i:int = 0; i < _pointers.length; i++) {
				if (_pointers[i].bandwidth > _endR - _endL) throw new IllegalOperationError("the FaderPointers's bandwidth exceeds the interval between the Fader's endL and endR.");
			}
		}
		
		public function get pointers():Array {
			return _pointers;
		}
		
		public function set pointers(value:Array):void {
			_pointers = value;
			if (_pointers.length > 0) {
				for (var i:int = 0; i < _pointers.length; i++) {
					if (!_pointers[i] is FaderPointer) throw new IllegalOperationError("pointers array must contain FaderPoitners only.");
				}
			}
			endL = _endL;
			endR = _endR;
		}
		
		//end of accessors
		//------------------------------------------------------------------------------------------------
		
		
		//temp
		//------------------------------------------------------------------------------------------------
		
		private static var d:Number;
		private static var k:int;
		//end of temp
		//------------------------------------------------------------------------------------------------
	}
}